Skip to content

fix(analytics): detect AI agents from the variables that they set - #859

Merged
nieblara merged 3 commits into
mainfrom
fix/agent-context-detection
Oct 28, 2026
Merged

nieblara merged 3 commits into
mainfrom
fix/agent-context-detection

Conversation

@nieblara

@nieblara nieblara commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Requirements

  • I have added test coverage for new or changed functionality
  • I have followed the repository's pull request submission guidelines
  • I have validated my changes against all supported platform versions

I ran the tests and the binary on Linux arm64 only.

Related issues

This PR fixes the agent detection that REL-12753 (#659) added.

Describe the solution you've provided

The agent_context event property tells us which AI coding agent ran ldcli. In the last 30 days, Mixpanel showed almost no named agents. Only one account sent claude-code, and only one user sent aider. All other agent runs went to unknown-non-interactive.

The cause is the list of environment variables in cmd/analytics/known_agents.json. Each name was compared to the source code, documentation, and live environment of the agent.

Variable in the old list Result Variable that the agent sets
CLAUDE_CODE, CLAUDE_CODE_SESSION No version of Claude Code sets them CLAUDECODE=1, AI_AGENT
CURSOR_SESSION_ID No source sets it CURSOR_AGENT=1
CURSOR_TRACE_ID Cursor sets it for people in its terminal too Removed
CODEX_SESSION, CODEX_SANDBOX_ID Codex does not set them CODEX_THREAD_ID, CODEX_SESSION_ID, CODEX_CI
DEVIN_SESSION Devin does not set it AI_AGENT=devin_<version>_agent
GITHUB_COPILOT Copilot does not set it AI_AGENT, COPILOT_AGENT, COPILOT_CLI
WINDSURF_SESSION Windsurf does not set it WINDSURF_CASCADE_TERMINAL
CLINE_TASK_ID Cline does not set it CLINE_ACTIVE
AIDER_MODEL Users set it to configure Aider OR_APP_NAME=Aider

The first commit makes these changes:

  1. It reads AI_AGENT before the other variables. AI_AGENT is a shared convention that agents.md#136 describes. Claude Code, Copilot in VS Code, Devin, OpenCode, Crush, and Amp set it. The gh CLI and @vercel/detect-agent read it. The value starts with the agent name, for example claude-code_2-1-295_agent. ldcli matches the start of the value. An unknown name becomes ai-agent:<name> without the version.
  2. It replaces the list with variables that the agents set. It adds Gemini CLI, OpenCode, Kilo Code, Crush, Augment, Roo Code, Amp, and Amazon Q. Kilo Code also sets OPENCODE=1, so ldcli examines KILO first.
  3. It lets an entry match a value with equals or contains. Amazon Q, Aider, and the Cursor terminal role need this.
  4. It adds tests that use the variables that each agent sets. Other tests make sure that the old names and the variables that people set do not match.

The order of the checks stays the same: LD_CLI_AGENT, then the agent variables, then the TTY check, then the CI variables. Some Cursor versions set CI=1. Because ldcli examines CURSOR_AGENT before the CI variables, these runs now get the label cursor, not ci.

The second commit fixes a different problem in CLI Command Completed. Since #589 (version 1.16.2), Execute called os.Exit(1) before it sent the event. Thus ldcli did not send an error outcome. In the last 30 days, all 4,542 error events came from version 1.16.1 or earlier. Execute now sends the event before it exits. The exit code stays 1.

I ran both binaries in a Claude Code session against a local server that recorded the tracking requests:

main,   flags get with an error   CLI Command Run        agent_context=unknown-non-interactive
                                  (no CLI Command Completed event)
branch, flags get with an error   CLI Command Run        agent_context=claude-code
                                  CLI Command Completed  agent_context=claude-code outcome=error
main,   config --list             agent_context=unknown-non-interactive
branch, config --list             agent_context=claude-code

Describe alternatives you've considered

I did not use @vercel/detect-agent as a model. It detects Cursor with CURSOR_TRACE_ID and Copilot with COPILOT_MODEL. These variables also occur for people or come from the configuration of the user. I also did not use REPL_ID, TERM_PROGRAM, or the generic AGENT variable. Each of them also occurs when a person types the command.

Additional context

These limits remain:

  1. Cline and Roo Code set their variables only in a VS Code terminal. In background mode, they do not set a variable.
  2. Continue, Zed, Jules, and Warp do not set an agent variable.
  3. The name WINDSURF_CASCADE_TERMINAL comes from the jfrog-cli detector. The Windsurf source code is not public.

The dashboard counts will change slowly, because only users who upgrade send the new labels. A blank agent_context does not always mean a person at a terminal. Versions before 3.0.0 do not send the property, and these versions sent about 45% of the blank runs.

🤖 Generated with Claude Code


Note

Overview
Fixes agent_context analytics so ldcli recognizes AI coding agents instead of labeling almost everything unknown-non-interactive.

Detection now reads AI_AGENT (after LD_CLI_AGENT) using configurable prefix rules and a sanitized ai-agent:<name> fallback for unknown agents. known_agents.json is replaced with env vars agents actually set (e.g. CLAUDECODE, CURSOR_AGENT, CODEX_*, COPILOT_*) and supports equals / contains value checks to avoid false positives (Aider, Amazon Q, Cursor terminal role). Legacy variable names that agents never set are removed.

Execute in cmd/root.go no longer calls os.Exit(1) before analytics flush; failed commands now emit CLI Command Completed with outcome=error before exiting.

Reviewed by Cursor Bugbot for commit a22f976. Bugbot is set up for automated code reviews on this repo. Configure here.

Ramon Niebla and others added 2 commits October 9, 2026 05:44
Most environment variables in known_agents.json were not real. No agent
sets CLAUDE_CODE, CURSOR_SESSION_ID, CODEX_SESSION, DEVIN_SESSION,
GITHUB_COPILOT, WINDSURF_SESSION, or CLINE_TASK_ID. Users set
AIDER_MODEL themselves. Thus ldcli reported almost all agent runs as
unknown-non-interactive.

This change replaces the list with variables from the source code or
documentation of each agent. It also reads AI_AGENT, a variable that
many agents set. The value starts with the agent name, for example
claude-code_2-1-295_agent. An unknown name becomes ai-agent:<name>.

An entry can now match a value with equals or contains. Amazon Q, Aider,
and the Cursor terminal role need this. The tests use the variables
that each agent sets, and they make sure that the old names do not match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Execute called os.Exit(1) before it sent CLI Command Completed. os.Exit
stops the process immediately, so ldcli did not send an error outcome
after version 1.16.2. Execute now sends the event, waits for it, and
then exits with code 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nieblara
nieblara requested a review from a team as a code owner October 9, 2026 05:45
@nieblara
nieblara merged commit a8a5a6f into main Oct 28, 2026
8 of 9 checks passed
@nieblara
nieblara deleted the fix/agent-context-detection branch October 28, 2026 20:36

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit a22f976. Configure here.

{"env_var": "CLINE_ACTIVE", "label": "cline"},
{"env_var": "ROO_ACTIVE", "label": "roo-code"},
{"env_var": "ROO_CLI_RUNTIME", "label": "roo-code"},
{"env_var": "AMP_CURRENT_THREAD_ID", "label": "amp"},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amp runs mislabeled as Claude Code

Medium Severity

Amp sets CLAUDECODE=1 along with AMP_CURRENT_THREAD_ID. The agents list matches CLAUDECODE first, so when AI_AGENT is absent the fallback path labels those runs claude-code instead of amp. Other detectors check Amp’s variable before CLAUDECODE for this reason.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a22f976. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants